package com.astoolkit.workflow
{
    import flash.events.EventDispatcher;
    import flash.net.registerClassAlias;
    import flash.utils.getQualifiedClassName;
    import flash.utils.setTimeout;
    
    import mx.logging.ILogger;
    import mx.logging.Log;
    import mx.rpc.Fault;
    import mx.utils.ObjectUtil;	
	
    [DefaultProperty("tasks")]
    public class Workflow 
    	extends BaseTask 
    	implements IWorkflow
    {
		public namespace WORKFLOW_PRIVATE = "http://www.astoolkit.com/ns/workflow_private";

		protected static const LOGGER : ILogger =
			Log.getLogger( getQualifiedClassName( Workflow ).replace(/:+/g, "." ) );
		

		protected var _root : IWorkflow;
		
		protected var _insert : Array;
		
		protected var _childrenDelegate : WorkflowDelegate;
		
		protected var _iterate : Boolean = false;
		
        protected var _flow : String = Flow.SERIAL;
       
       
        [ArrayElementType("com.astoolkit.workflow.IWorkflowTask")]
        protected var _processedTasks : Array = []
       
        protected var _nextTaskIndex : int = 0;
       
        [ArrayElementType("com.astoolkit.workflow.IWorkflowTask")]
        protected var _tasks : Array = [];

       
        public function Workflow() {
            super();
			_childrenDelegate = new WorkflowDelegate( this );
        }

		public function get iterate() : Boolean
		{
			return _iterate;
		}
		
		public function set iterate( inIterate: Boolean ) : void
		{
			_iterate = inIterate;
		}

		
		public function set insert( inValue : Array ) : void
		{
			if( _insert )
				throw new Error( "insert cannot be redefined" );
			_insert = inValue;
		}
       
       
        [Inspectable(defaultValue="serial", enumeration="parallel,serial,none")]
        public function get flow() : String
        {
            return _flow;
        }
       
        public function set flow( inFlow : String ) : void
        {
            if( inFlow != Flow.SERIAL && inFlow != Flow.PARALLEL && inFlow != Flow.NONE )
            {
                throw new Error(
                    "Workflow 'flow' property can only be either 'parallel' or 'serial' or 'none': " +
                    "found '" + inFlow + "'" );
            }
            _flow = inFlow;
        }
		
		[Bindable]
        [ArrayElementType("com.astoolkit.workflow.IWorkflowTask")]
        public function get tasks() : Array
        {
            return _tasks;   
        }
        
        public function set tasks( inTasks : Array ) : void
        {
			if( _tasks != null && _tasks.length > 0 )
				throw new Error( "Tasks list cannot be overridden" );
            _tasks = inTasks;
            if( _tasks != null )
            {
                for each( var task : IWorkflowTask in _tasks )
                {
                    task.delegate = _childrenDelegate;
					task.parent = this;
                }
            }
        }


		public function get declaredTasks() : Array
		{
			return _tasks;
		}
		       
        override public function begin() : void
        {
			if(!_delegate)
				_delegate = new WorkflowDelegate( this );
			initContext();
			super.begin();
			if( !_parent )
				prepare();
            if( _tasks == null || _tasks.length == 0 )
            {
                LOGGER.warn( "Workflow {0} has no tasks to perform", _name );
                dispatchEvent( new WorkflowEvent( WorkflowEvent.COMPLETED ) );
                return;
            }
            runNextTask();
        }
       
		protected function initContext() : void
		{
			var aContext : IWorkflowContext;
			if( !_delegate.context )
				aContext = new DefaultContext();
			else
				aContext = _delegate.context;
			_delegate.context = aContext;
			_childrenDelegate.context = aContext;
		}
		
        protected function runNextTask() : void
        {
            if( _nextTaskIndex == _tasks.length )
            {
				complete();
                return;
            }
            var task : IWorkflowTask = _tasks[ _nextTaskIndex ];
            _nextTaskIndex++;
			if( task is IWorkflow && IWorkflow( task ).flow == Flow.NONE )
			{
				runNextTask();
				return;
			}
            LOGGER.debug(
                "Next task '{0}'", task.title );
            if( task.enabled )
            {
                _processedTasks.push( task );
				runSubTask( task );
            } else {
                runNextTask();
                return;
            }
            if( _flow == Flow.PARALLEL )
            {
                runNextTask();
            }
        }
		
		WORKFLOW_PRIVATE function onSubtaskSuspended( inTask : IWorkflowTask ) : void
		{
		}
		
		WORKFLOW_PRIVATE function onSubtaskResumed( inTask : IWorkflowTask ) : void
		{
		}
		
		WORKFLOW_PRIVATE function onSubtaskBegin( inTask : IWorkflowTask ) : void
		{
		}
		
		WORKFLOW_PRIVATE function onSubtaskProgress( inTask : IWorkflowTask ) : void
		{
		}
		

        WORKFLOW_PRIVATE function onSubtaskWarning( inTask : IWorkflowTask, inMessage : String, inError : Error ) : void
        {
			WORKFLOW_PRIVATE::onSubtaskCompleted( inTask );
        }
       
        WORKFLOW_PRIVATE function onSubtaskFault( inTask : IWorkflowTask, inMessage : String, inFault : Fault ) : void
        {
            if( inTask.failurePolicy == FailurePolicy.ABORT )
            {
                if( _parent == null )
                {
                    cleanUp();                   
                }
				_delegate.onAbort( this, inMessage, inFault );
            }
            else
            {
                WORKFLOW_PRIVATE::onSubtaskCompleted( inTask );
            }
        }
       
		WORKFLOW_PRIVATE function onSubtaskAbort( inTask : IWorkflowTask, inMessage : String, inFault : Fault ) : void
		{
			WORKFLOW_PRIVATE::onSubtaskCompleted( inTask );
		}
	
		WORKFLOW_PRIVATE function onSubtaskCompleted( inTask : IWorkflowTask ) : void
        {
            _processedTasks.splice( _processedTasks.indexOf( inTask ), 1 );
			inTask.delegate = null;
			
            if( _flow == Flow.SERIAL && _nextTaskIndex < _tasks.length )
            {
                runNextTask();
                return;
            }
            else
            {
                if( _processedTasks.length == 0 )
                {
                    complete();
                }
            }
        }
       
		protected function runSubTask( inTask : IWorkflowTask, inNow : Boolean = false ) : void
		{
			if( _status == TaskStatus.SUSPENDED ) {
				addResumeCallBack( 
					function() : void 
					{
						runSubTask( inTask, inNow );
					} );
				return;
			}
			if( inNow || inTask.delay == 0 )
			{
				try {
					inTask.begin();
				}
				catch( taskError : Error )
				{
					_childrenDelegate.onFault(inTask, taskError.message, new Fault( taskError.errorID.toString(), taskError.message, taskError.getStackTrace() ) );
					LOGGER.error( taskError.getStackTrace() );
				}
			}
			else
			{
				setTimeout( runSubTask, inTask.delay, inTask, true );
			}
		}
		
        override public function cleanUp() : void
        {
			super.cleanUp();
            for each( var task : IWorkflowTask in _processedTasks )
            {
				task.delegate = null;
				task.cleanUp();
            }
            _processedTasks = [];
            _nextTaskIndex = 0;
        }

		override public function prepare() : void
		{
			super.prepare();
			_delegate.onPrepare( this );
			_processedTasks = [];
			_nextTaskIndex = 0;
			
			var task : IWorkflowTask;
			processInserts();
			for each( task in declaredTasks )
			{
				task.delegate = _childrenDelegate;
				task.prepare();
			}
				

		}

		override protected function complete() : void
		{
			if( !iterate )
			{
				for each( var task : IWorkflowTask in _tasks )
					task.delegate = null;
				super.complete();
				return;
			}
			prepare();
			setTimeout(runNextTask, 10);
		}
		
		protected function processInserts() : void
		{
			var task : IWorkflowTask;
			if( _insert ) 
			{
				for each( var insertEntry : Insert in _insert )
				{
					if( insertEntry.relativeTo != null )
						insertEntry.parent = insertEntry.relativeTo.parent;
					
					var childrenTasks : Array = insertEntry.parent.declaredTasks;
					var insertionPoint : int;
					if( insertEntry.relativeTo != null )
					{
						var i : int;
						for( i = 0; i < childrenTasks.length; i++ )
						{
							if( insertEntry.relativeTo == childrenTasks[ i ] )
							{
								break;
							}
						}
						insertionPoint = insertEntry.position == Insert.BEFORE ? i : i + 1;
					}
					else
					{
						// if children length == 0 we always add the task starting at 0,
						// otherwise depending on the position value we add 
						// to the beginning or end of the tasks array
						insertionPoint = childrenTasks.length > 0 ?
							(insertEntry.position == Insert.BEFORE ? 0 : childrenTasks.length ) : 0;
					}
					
					for each( task in insertEntry.tasks )
					{
						childrenTasks.splice( insertionPoint, 0, task );
						insertionPoint++;
					}
					
				}
			}
			_insert = [];
			
		}
	}
}
